package hxsigslot;

import haxe.macro.Expr;
import haxe.macro.Context;

class SigSlot 
{
	
	@:macro public static function build( mainArg : Expr ) {
        var pos = haxe.macro.Context.currentPos();
        var tstring = TPath( { pack : [], name : "String", params : [], sub : null } );
		var tvoid = TPath( { pack : [], name : "Void", params : [], sub : null } );
		var thash = TPath( { sub : null, name : "Hash", pack : [], params : [TPType(TPath( { sub : null, name : "Array", pack : [], params : [TPType(TPath( { sub : null, name : "Dynamic", pack : [], params : [] } ))] } ))] } );
		
		var fields = [];
		
		var inherit : Bool = false;
		
		switch( mainArg.expr ) {
		case EBlock(explist):
		
			for ( exp in explist ) {
				switch( exp.expr ) {
				case EFunction( f ):
					
					{ // Bind function
						var argTypes = [];
						for ( arg in f.args ) argTypes.push( arg.type );
						var farg : FunctionArg = { name : "listener", opt : false, type : TFunction( argTypes, tvoid ), value : null };
						var fbody = Context.parse( 
										"{ var listenersHash = _sigslot_getListenersHash();	var listeners = listenersHash.get( \"" + f.name + "\" );" +
										"if ( listeners == null ) { listeners = []; listenersHash.set( \"" + f.name + "\", listeners ); } listeners.push(listener); }"
										, pos );
							
						fields.push( { expr : EFunction( { name : "bind" + f.name, args : [ farg ], ret : tvoid, expr : fbody } ), pos : pos } );
					}
						
					{ // Dispatch function
						var fargs = [];
						var sbody = "{ if ( _sigslot_listenerHash == null ) return;	var list = _sigslot_listenerHash.get( \"" + f.name + "\"); if ( list == null ) return; for ( listener in list ) { listener(";
						
						for ( i in 0...f.args.length ) {
							var fa = f.args[i];
							sbody += fa.name + if ( i != f.args.length - 1 ) "," else "";
							fargs.push ( { name : fa.name, opt : false, type : fa.type, value : null } );
						}
						sbody += " ); } }";
						
						var fbody = Context.parse( sbody, pos );
						fields.push( { expr : EFunction( { name : "dispatch" + f.name, args : fargs, ret : tvoid, expr : fbody } ), pos : pos } );
					}
					
					{ // Unbind function
						var argTypes = [];
						for ( arg in f.args ) argTypes.push( arg.type );
						var farg : FunctionArg = { name : "listener", opt : false, type : TFunction( argTypes, tvoid ), value : null };
						var fbody = Context.parse( 
										"{ if ( _sigslot_listenerHash == null ) return; var _list = _sigslot_listenerHash.get( \"" + f.name + "\"); if ( _list == null ) return; _list.remove(listener); }"
										, pos );
							
						fields.push( { expr : EFunction( { name : "unbind" + f.name, args : [ farg ], ret : tvoid, expr : fbody } ), pos : pos } );
					}
					
				case EConst( c ):
					switch ( c ) {
					case CIdent( identName ):
						if ( identName == "inherit" ) {
							inherit = true;
						}
					default:
						Context.error("Expression should be a function or \"inherit;\"" + exp, exp.pos);
					}
					
					
					
				default:
					Context.error("Expression should be a function or \"inherit\"" + exp, exp.pos);
				}
			}
		default: 
			Context.error("Argument should be a block of functions.", mainArg.pos);
		}
		
		if ( !inherit ) {
			fields.push( { expr : EVars( [ { name : "private___sigslot_listenerHash", type : thash, expr : null } ] ), pos : pos } );
			
			{
				var fbody = Context.parse( "{ if ( _sigslot_listenerHash == null ) _sigslot_listenerHash = new Hash<Array<Dynamic>>(); return _sigslot_listenerHash; }", pos );
				fields.push( { expr : EFunction( { name : "private___sigslot_getListenersHash", args : [], ret : thash, expr : fbody } ), pos : pos } );
			}
			
			{ // Unbind all function
				var fbody = Context.parse(
								"{_sigslot_listenerHash = null;}"
								, pos );
					
				fields.push( { expr : EFunction( { name : "inline__public__unbindAll", args : [], ret : tvoid, expr : fbody } ), pos : pos } );
			}
		}
		
        return { expr : EBlock(fields), pos : pos };
    }
	
}